package com.sloera.security;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Base64;

/**
 * 对称加密算法使用同一个密钥进行加密和解密，常用算法有DES、AES和IDEA等；
 * <br/>
 * 密钥长度由算法设计决定，AES的密钥长度是128/192/256位；
 * <br/>
 * 使用对称加密算法需要指定算法名称、工作模式和填充模式。
 *
 * @author liuwangyang
 * @version 1.0
 * @class com.sloera.security.encrypt
 * @date 2020-7-30
 */
public class encrypt {
    public static void main(String[] args) throws GeneralSecurityException {
        ecbEncryptTemp();
        cbcEncryptTemp();
    }

    /**
     * CBC模式
     *
     * @return void
     * @date 2020-7-30
     */
    private static void cbcEncryptTemp() throws GeneralSecurityException {
        String message = "Hello, world!";
        System.out.println("Message: " + message);
        // 256位密钥 = 32 bytes Key
        byte[] key = "1234567890abcdef1234567890abcdef".getBytes(StandardCharsets.UTF_8);
        // 加密
        byte[] data = message.getBytes(StandardCharsets.UTF_8);
        byte[] encrypted = cbcEncrypt(key, data);
        System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
        // 解密
        byte[] decrypted = cbcDecrypt(key, encrypted);
        System.out.println("Decrypted: " + new String(decrypted, StandardCharsets.UTF_8));
    }

    /**
     * CBC加密
     *
     * @param key 256bit位密钥
     * @param input IV+待解密字符
     * @return byte[] 加密后字符
     * @date 2020-7-30
     */
    private static byte[] cbcDecrypt(byte[] key, byte[] input) throws GeneralSecurityException {
        // 把input分割成IV和密文
        byte[] iv = new byte[16];
        byte[] encrypted = new byte[input.length - 16];
        System.arraycopy(input, 0, iv, 0, 16);
        System.arraycopy(input, 16, encrypted, 0, encrypted.length);
        //解密
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        IvParameterSpec ivps = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivps);
        return cipher.doFinal(encrypted);
    }

    /**
     * CBC解密
     *
     * @param key 256bit位密钥
     * @param encrypted 待解密字符
     * @return byte[] 解密后字符
     * @date 2020-7-30
     */
    private static byte[] cbcEncrypt(byte[] key, byte[] data) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        // CBC模式需要生成一个16bytes的initialization vector
        SecureRandom sr = SecureRandom.getInstanceStrong();
        byte[] iv = sr.generateSeed(16);
        IvParameterSpec ivps = new IvParameterSpec(iv);
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivps);
        byte[] encrypted = cipher.doFinal(data);
        return join(iv, encrypted);
    }

    /**
     * 将IV和密文拼接
     *
     * @param iv initialization vector
     * @param encrypted 密文
     * @return byte[] iv在前，密文在后
     * @date 2020-7-30
     */
    private static byte[] join(byte[] iv, byte[] encrypted) {
        byte[] r = new byte[iv.length + encrypted.length];
        System.arraycopy(iv, 0, r, 0, iv.length);
        System.arraycopy(encrypted, 0, r, iv.length, encrypted.length);
        return r;
    }

    /**
     * ECB模式-最简单的AES加密模式
     *
     * @return void
     * @date 2020-7-30
     */
    private static void ecbEncryptTemp() throws GeneralSecurityException {
        String message = "Hello, world!";
        System.out.println("Message: " + message);
        // 128位密钥 = 16 bytes Key
        byte[] key = "1234567890abcdef".getBytes(StandardCharsets.UTF_8);
        // 加密
        byte[] data = message.getBytes(StandardCharsets.UTF_8);
        byte[] encrypted = ecbEncrypt(key, data);
        System.out.println("Encrypted: " + Base64.getEncoder().encodeToString(encrypted));
        // 解密
        byte[] decrypted = ecbDecrypt(key, encrypted);
        System.out.println("Decrypted: " + new String(decrypted, StandardCharsets.UTF_8));
    }

    private static byte[] ecbDecrypt(byte[] key, byte[] encrypted) throws java.security.GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.DECRYPT_MODE, keySpec);
        return cipher.doFinal(encrypted);
    }

    private static byte[] ecbEncrypt(byte[] key, byte[] data) throws java.security.GeneralSecurityException {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec);
        return cipher.doFinal(data);
    }
}
